/*
 *MIT License
 *
 *Copyright (c) 2019 chenshuai cs4380@163.com
 *
 *Permission is hereby granted, free of charge, to any person obtaining a copy
 *of this software and associated documentation files (the "Software"), to deal
 *in the Software without restriction, including without limitation the rights
 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *copies of the Software, and to permit persons to whom the Software is
 *furnished to do so, subject to the following conditions:
 *
 *The above copyright notice and this permission notice shall be included in all
 *copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *SOFTWARE.
 */

package com.cs.cslc.auth.filter;

import com.alibaba.fastjson.JSON;
import com.cs.cslc.auth.bean.JWTUserBean;
import com.cs.cslc.auth.realm.JWTToken;
import com.cs.cslc.auth.service.UserService;
import com.cs.cslc.auth.utils.JWTUtil;
import com.cs.cslc.common.constant.AuthExceptionConstant;
import com.cs.cslc.common.constant.HttpHeadersConstant;
import com.cs.cslc.common.context.BaseContextHandler;
import com.cs.cslc.common.enums.AuthenticationEnums;
import com.cs.cslc.common.pojo.AuthInfoExceptionDTO;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * JWTFilter jwt过滤器.
 * <p>
 * http请求拦截，验证jwt是否携带，是否正确.
 * 拦截优先级大于HandlerInterceptorAdapter.
 * </p>
 *
 * @author cs4380 https://gitee.com/xhbug_cs4380  cs4380@163.com
 * @version 1.0
 * @since 2019-05-06 19:23
 */
@Slf4j
public class TokenAuthFilter extends BasicHttpAuthenticationFilter {

    @Autowired
    private UserService userService;

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        String token = this.getToken(request);
        // 存在token则缓存token信息到内存中
        if (StringUtils.isNotBlank(token)) {
            // 解析token获取用户信息
            JWTUserBean userBean = JWTUtil.getUserBeanByToken(token);
            // 封装用户信息
            BaseContextHandler.setUserId(userBean.getUserId());
            BaseContextHandler.setAccount(userBean.getAccount());
            BaseContextHandler.setName(userBean.getName());
            BaseContextHandler.setTenantId(userBean.getTenantId());
            BaseContextHandler.setDeparts(userBean.getDepts());
            BaseContextHandler.setRoles(userBean.getRoles());
            BaseContextHandler.setIsSuperAdmin(userBean.getIsSuperAdmin());
        }
        return super.preHandle(request, response);
    }

    /**
     * 是否允许访问,验证token是否正确
     *
     * @param request     request
     * @param response    response
     * @param mappedValue mappedValue
     * @return true: 允许访问,false:访问限制
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = this.getToken(request);
        String requestURL = httpServletRequest.getRequestURI();
        String method = httpServletRequest.getMethod();
        // 1.判断当前请求是否放行地址
        if (userService.isExcludeURL(requestURL)) {
            // 直接放行不验证token
            return true;
        } else {
            // 并且验证token信息
            if (StringUtils.isNotBlank(token)) {
                try {
                    // 2.验证当前请求token信息，密码是否正确、是否过期、禁用等
                    JWTToken jwtToken = new JWTToken(token);
                    // login后调用ShiroDatabaseRealm.doGetAuthenticationInfo()方法
                    this.getSubject(request, response).login(jwtToken);
                } catch (Exception e) {
                    this.forwardRequest(request, response, e.getMessage());
                    return false;
                }

                // 3.验证用户角色和权限信息，判断是否允许访问接口地址
                boolean isPermitted = userService.isPermitted(requestURL, method);
                if (!isPermitted) {
                    log.info("requestURL={} method={}", requestURL, method);
                    this.forwardRequest(request, response,
                            JSON.toJSONString(AuthInfoExceptionDTO.builder().exceptionType(AuthInfoExceptionDTO.AUTH_EXCEPTION_TYPE)
                                    .message(AuthenticationEnums.AUTH_FAIL.getValue())
                                    .statues(AuthenticationEnums.AUTH_FAIL.getCode())
                                    .build()));
                    return false;
                }
            } else {
                log.info("没有权限访问地址 requestURL={} method={}", requestURL, method);
                // 4.当前请求不存在token并且不是放行地址，则抛出无权限异常
                this.forwardRequest(request, response,
                        JSON.toJSONString(AuthInfoExceptionDTO.builder().exceptionType(AuthInfoExceptionDTO.AUTH_EXCEPTION_TYPE)
                                .message(AuthenticationEnums.AUTH_FAIL.getValue())
                                .statues(AuthenticationEnums.AUTH_FAIL.getCode())
                                .build()));
                return false;
            }
        }
        return true;
    }

    /**
     * 检测用户是否已登录
     * 检测header里面是否包含Authorization字段即可
     *
     * @param request request
     * @return token
     */
    private String getToken(ServletRequest request) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        return httpServletRequest.getHeader(HttpHeadersConstant.HEADERS_COOKIE_USER_TOKEN);
    }

    /**
     * 清除内存缓存中token
     *
     * @param request   request
     * @param response  response
     * @param exception exception
     * @throws Exception exception
     */
    @Override
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
        String token = this.getToken(request);
        // 存在token则清除缓存中的token
        if (StringUtils.isNotBlank(token)) {
            BaseContextHandler.remove();
        }
        super.afterCompletion(request, response, exception);
    }

    /**
     * 异常请求转发到，统一报错接口，对异常进行捕获解析，并相应给前端
     * <p>
     * 处理接口：com.cs.cslc.common.rest.AuthenticationExceptionController
     *
     * @param request       request
     * @param response      response
     * @param exceptionInfo 异常信息：com.cs.cslc.common.pojo.AuthInfoExceptionDTO
     */
    @SneakyThrows
    private void forwardRequest(ServletRequest request, ServletResponse response, String exceptionInfo) {
        request.setAttribute(AuthExceptionConstant.AUTH_EXCEPTION_INFO, exceptionInfo);
        // 将异常转发到异常处理接口
        request.getRequestDispatcher(AuthExceptionConstant.AUTH_EXCEPTION_API).forward(request, response);
    }
}